#include "network_load.hh"
#include <string>
#include <exception>
#include <fstream>
#include <iostream>
#include <list>
#include <sstream>
#include <stdexcept>
#include <algorithm>
#include <cstring>
#include <utility>

network_load::network_load(std::string ethernet_data_fileName, std::string eth_name) : ethernet_dataFile(std::move(ethernet_data_fileName)), eth_dev(std::move(eth_name)) {
    this->init_network_monitor();

}
uint64_t network_load::parse_ethernet_device() {


    if(this->timeStamp + std::chrono::milliseconds(1000) > std::chrono::steady_clock::now()) {
        return 0;
    } else {
        this->timeStamp = std::chrono::steady_clock::now();
    }


    std::ifstream ethernetFile;
    try {
        ethernetFile.open(this->ethernet_dataFile);
    } catch (std::ifstream::failure &e) {
        throw std::runtime_error("Exception: " +this->ethernet_dataFile + std::string(e.what()));
    }


    if (!ethernetFile.is_open()) {
        return 0;
    }

    std::string line;
    std::list<std::string> identifiers{"RXbytes",
                                       "RXpackets",
                                       "RXerrs",
                                       "RXdrop",
                                       "RXfifo",
                                       "RXframe",
                                       "RXcompressed",
                                       "RXmulticast",
                                       "TXbytes",
                                       "TXpackets",
                                       "TXerrs",
                                       "TXdrop",
                                       "TXfifo",
                                       "TXcolls",
                                       "TXcarrier",
                                       "TXcompressed"
    };

    std::map<std::string, std::string>::iterator it;
    it = this->networkstatMap.find(std::string("IF"));
    if (it == this->networkstatMap.end()) {
        this->networkstatMap.insert(std::pair<std::string, std::string>(std::string("IF"), this->eth_dev));
    }
    while (std::getline(ethernetFile, line)) {
        std::string str_line(line);
        auto lit = identifiers.begin();
        if ((str_line.find(this->networkstatMap["IF"])) != std::string::npos) {
            std::string line_;
            std::istringstream ss(str_line);
            this->isDeviceAvailable = true;
            while (std::getline(ss, line_, ' ')) {
                try {
                    uint64_t parsedULL = std::stoull(line_, nullptr, 10);
                    if (lit != identifiers.end()) {
                        it = networkstatMap.find(*lit);
                        if (it == networkstatMap.end()) {
                            networkstatMap.insert(std::pair<std::string, std::string>(*lit,
                                    std::to_string(parsedULL)));
                        } else {
                            it->second = std::to_string(parsedULL);
                        }
                        lit++;
                    }
                } catch (std::exception &e) {
                    e.what();
                }
            }
            break;
        }
    }
    ethernetFile.close();

    return 0;
}

void network_load::init_network_monitor() {
    this->timeBefore = std::chrono::steady_clock::now();
    this->timeBefore_tx = std::chrono::steady_clock::now();
    this->timeBefore_rx = std::chrono::steady_clock::now();
    this->parse_ethernet_device();
    try {
        this->m_totalTransceivedBytes = std::stoull(this->networkstatMap["RXbytes"], nullptr, 10);
        this->m_totalTransceivedBytes += std::stoull(this->networkstatMap["TXbytes"], nullptr, 10);
        this->m_totalTransmittedBytes = this->get_TXBytes_sincestartup();
        this->m_totalReceivedBytes = this->get_RXBytes_sinceStartup();
    } catch (std::exception &e) {
        e.what();
    }
}
uint64_t network_load::get_TXBytes_sincestartup() {
    this->parse_ethernet_device();
    uint64_t transceivedBytes = 0;
    try {
        transceivedBytes += std::stoull(this->networkstatMap["RXbytes"], nullptr, 10);
    } catch (std::exception &e) {
        e.what();
        transceivedBytes = 0;
    }
    return transceivedBytes;
}
uint64_t network_load::get_RXBytes_sinceStartup() {
    this->parse_ethernet_device();
    uint64_t transceivedBytes = 0;
    try {
        transceivedBytes += std::stoull(this->networkstatMap["TXbytes"], nullptr, 10);
    } catch (std::exception &e) {
        e.what();
        transceivedBytes = 0;
    }
    return transceivedBytes;
}
bool network_load::is_device_up() const {
    return this->isDeviceAvailable;
}


std::string network_load::get_devicename() {
    return this->eth_dev;
}
uint64_t network_load::get_rxBytes_persecond(){
    uint64_t oldBytesReceived = this->m_totalReceivedBytes;
    std::chrono::time_point<std::chrono::steady_clock> oldclock = this->timeBefore_rx;

    this->parse_ethernet_device();
    this->m_totalReceivedBytes = this->get_RXBytes_sinceStartup();

    this->timeBefore_rx = std::chrono::steady_clock::now();
    std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds> (this->timeBefore_rx - oldclock);

    uint64_t Bytes = this->m_totalReceivedBytes - oldBytesReceived;
    Bytes *= 1000;
    if (static_cast<unsigned long>(msec.count()) <= 0) {
        Bytes /= 1;
    } else {
        Bytes /= static_cast<unsigned long>(msec.count());
    }
    return Bytes;

}
uint64_t network_load::get_txBytes_persecond(){
    uint64_t oldBytesTransceived = this->m_totalTransceivedBytes;
    std::chrono::time_point<std::chrono::steady_clock> oldclock = this->timeBefore;

    this->parse_ethernet_device();
    try {
        this->m_totalTransceivedBytes = std::stoull(this->networkstatMap["RXbytes"], nullptr, 10);
        this->m_totalTransceivedBytes += std::stoull(this->networkstatMap["TXbytes"], nullptr, 10);
    } catch (std::exception &e) {
        e.what();
        return 0;
    }
    this->timeBefore = std::chrono::steady_clock::now();
    std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds> (this->timeBefore - oldclock);

    uint64_t Bytes = this->m_totalTransceivedBytes - oldBytesTransceived;
    Bytes *= 1000;
    if (static_cast<unsigned long>(msec.count()) <= 0) {
        Bytes /= 1;
    } else {
        Bytes /= static_cast<unsigned long>(msec.count());
    }
    return Bytes;
}
cJSON* network_load::to_json()
{
    cJSON* cjson = cJSON_CreateObject();

    uint64_t rx = get_rxBytes_persecond();
    uint64_t tx = get_txBytes_persecond();

    cJSON_AddNumberToObject(cjson, "rx_Bytes_persecond", rx); 
    cJSON_AddNumberToObject(cjson, "tx_Bytes_persecond", tx); 

     return cjson;
}
const char*  network_load::get_name()
{
    return "network_load";
}
network_load::~network_load()
{
    this->networkstatMap.clear();   
}